package com.bifang.core.oplog.aspect;

import cn.hutool.core.util.IdUtil;
import cn.hutool.extra.servlet.ServletUtil;
import cn.hutool.json.JSONUtil;
import cn.hutool.system.HostInfo;
import cn.hutool.system.SystemUtil;
import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
import com.bifang.common.constant.CacheConst;
import com.bifang.common.constant.enums.common.AuTypeEnum;
import com.bifang.common.constant.enums.common.BoolEnum;
import com.bifang.common.constant.enums.common.EquipmentTypeEnum;
import com.bifang.common.constant.enums.common.ErrorEnum;
import com.bifang.common.constant.enums.common.OperatingEnum;
import com.bifang.common.constant.enums.common.SysPermissionEnum;
import com.bifang.common.exception.AppException;
import com.bifang.common.util.CommonSecureUtil;
import com.bifang.common.util.OplogUtil;
import com.bifang.common.helper.RedisHelper;
import com.bifang.common.util.ServiceRequestUtil;
import com.bifang.common.util.SessionUtil;
import com.bifang.common.vo.BaseAuditVO;
import com.bifang.common.vo.BasePageVO;
import com.bifang.common.vo.BaseVO;
import com.bifang.common.vo.OplogApiVO;
import com.bifang.common.vo.OplogInfo;
import com.bifang.common.vo.RemarksInfoVO;
import com.bifang.common.vo.SelectVO;
import com.bifang.common.vo.SessionInfo;
import com.bifang.common.vo.operation.OperationPeriodVO;
import com.bifang.common.annotations.OpApi;
import com.bifang.common.annotations.SaveOplog;
import com.bifang.core.dao.model.Oplog;
import com.bifang.core.dao.model.OplogFunction;
import com.bifang.core.dao.model.OplogHistory;
import com.bifang.core.dao.service.OplogFunctionService;
import com.bifang.core.dao.service.OplogHistoryService;
import com.bifang.core.dao.service.OplogService;
import com.bifang.core.dict.integration.DictIntegration;
import com.bifang.core.oplog.integration.OperationPeriodIntegration;
import com.bifang.core.oplog.integration.OplogIntegration;
import java.io.File;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeanWrapper;
import org.springframework.beans.BeanWrapperImpl;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import org.springframework.web.multipart.MultipartFile;

/***
 *日志处理切面
 * chenzh
 * 2020-01-15
 * */
@Aspect
@Component
@Order(2)
public class OplogApiAspect {
    private static final Logger logger = LoggerFactory.getLogger(OplogApiAspect.class);

    @Autowired OplogIntegration oplogIntegration;
    @Autowired OplogService oplogService;

    @Value("${system.config.oplog.history.flag}")
    private Boolean oplogHistoryFlag;

    @Autowired OplogHistoryService oplogHistoryService;
    @Autowired
    RedisHelper redisHelper;
    @Autowired OplogFunctionService oplogFunctionService;
    @Autowired OperationPeriodIntegration operationPeriodIntegration;
    @Autowired private DictIntegration dictIntegration;
    /** 是否保存请求到数据中 */
    @Value("${system.config.saveOplogApi}")
    private Boolean saveOplogApi;

    @Value("${system.config.token.timeout}")
    private Long timeout;

    @Value("${system.config.token.app.timeout}")
    private Long appTimeout;

    @Value("${system.config.user.encryption.flag}")
    private Boolean encryptionFlag;

    @Before(
            "(execution(* com.bifang..*.integration..*.*(..))||execution(* com.jwsoft..*.integration..*.*(..)))&&@annotation(opApi)")
    public void before(JoinPoint joinPoint, OpApi opApi) throws Throwable {}

    @Around(
            "(execution(* com.bifang..*.integration..*.*(..))||execution(* com.jwsoft..*.integration..*.*(..)))&&@annotation(opApi)")
    public Object doAround(ProceedingJoinPoint joinPoint, OpApi opApi) throws Throwable {
        OplogInfo oplogInfo = OplogUtil.getOplogInfo();
        Long oplodId = null;
        if (oplogInfo != null) {
            oplodId = oplogInfo.getOplogId();
        }

        // 校验功能业务期控制规则
        OplogFunction oplogFunction = oplogIntegration.getFunction(opApi.funcCode());
        String logFlag = BoolEnum.TRUE.getType();
        if (!opApi.savelog()) {
            logFlag = BoolEnum.FALSE.getType();
        }
        Signature signature = joinPoint.getSignature();
        MethodSignature methodSignature = (MethodSignature) signature;
        Method targetMethod = methodSignature.getMethod();
        SaveOplog saveOplog = targetMethod.getAnnotation(SaveOplog.class);
        boolean auFlag = false;
        if (saveOplog != null) {
            String auType = saveOplog.auType().getType();
            if (auType.equals(AuTypeEnum.AUDIT.getType())
                    || auType.equals(AuTypeEnum.END.getType())) {
                auFlag = true;
            }
            logFlag = BoolEnum.TRUE.getType();
        }
        Object[] args = joinPoint.getArgs();
        if (oplogFunction != null) {
            String operating = oplogFunction.getOperating();
            if (BoolEnum.FALSE.getType().equals(logFlag)) {
                if (StringUtils.isNotBlank(oplogFunction.getLogFlag())) {
                    logFlag = oplogFunction.getLogFlag();
                }
            }
            String auditFlag = oplogFunction.getAuditFlag();
            String rollbackFlag = oplogFunction.getRollbackFlag();

            UpdateWrapper<Oplog> updateOplog = new UpdateWrapper<>();
            UpdateWrapper<OplogHistory> updateOplogHi = new UpdateWrapper<>();
            if (oplogInfo != null) {
                if (StringUtils.isBlank(operating)) {
                    operating = oplogInfo.getOperating();
                }
                if (StringUtils.isBlank(logFlag)) {
                    if (StringUtils.isNotBlank(oplogInfo.getLogFlag())) {
                        logFlag = oplogInfo.getLogFlag();
                    }
                }
                if (StringUtils.isBlank(auditFlag)) {
                    auditFlag = oplogInfo.getAuditFlag();
                }
                if (StringUtils.isBlank(rollbackFlag)) {
                    rollbackFlag = oplogInfo.getRollbackFlag();
                }

                oplogInfo.setOperating(operating);
                oplogInfo.setLogFlag(logFlag);
                oplogInfo.setRollbackFlag(rollbackFlag);
                oplogInfo.setAuditFlag(auditFlag);
                updateOplog
                        .lambda()
                        .set(Oplog::getAuditFlag, auditFlag)
                        .set(Oplog::getRollbackFlag, rollbackFlag);
                updateOplogHi
                        .lambda()
                        .set(OplogHistory::getAuditFlag, auditFlag)
                        .set(OplogHistory::getRollbackFlag, rollbackFlag);
            }

            if (StringUtils.isNotBlank(operating)
                    && !OperatingEnum.no.getType().equals(operating)) {
                // 获取操作的业务期
                OperationPeriodVO operationPeriodVO =
                        operationPeriodIntegration.getCurrentByOperating(operating);
                if (operationPeriodVO == null) {
                    throw new AppException("该业务有操作期控制，不在操作期内或者未设置操作期参数");
                }
                if (oplogInfo != null) {
                    oplogInfo.setDateYm(operationPeriodVO.getYm());
                    oplogInfo.setPeriodSameId(operationPeriodVO.getPeriodSameId());
                    if (operationPeriodVO.getYm() != null) {
                        updateOplog.lambda().set(Oplog::getDateYm, operationPeriodVO.getYm());
                        updateOplogHi
                                .lambda()
                                .set(OplogHistory::getDateYm, operationPeriodVO.getYm());
                    }
                    updateOplog
                            .lambda()
                            .set(Oplog::getPeriodSameId, operationPeriodVO.getPeriodSameId());
                    updateOplogHi
                            .lambda()
                            .set(
                                    OplogHistory::getPeriodSameId,
                                    operationPeriodVO.getPeriodSameId());
                }
            }

            if (oplogInfo != null) {
                // 处理方法内的摘要
                String summaryinfo = oplogFunction.getRemarks();
                if (BoolEnum.TRUE.getType().equals(logFlag)
                        && StringUtils.isNotBlank(summaryinfo)) {
                    try {
                        BeanWrapper beanWrapper = new BeanWrapperImpl(args[0]);
                        // 设置摘要信息
                        String remarks = "";
                        // 转义摘要
                        List<RemarksInfoVO> remarksInfoList =
                                JSONUtil.toList(summaryinfo, RemarksInfoVO.class);
                        if (!CollectionUtils.isEmpty(remarksInfoList)) {
                            int i = 1;
                            int size = remarksInfoList.size();
                            for (RemarksInfoVO remarksInfo : remarksInfoList) {
                                if (StringUtils.isNotBlank(remarksInfo.getTitle())) {
                                    remarks = remarks + remarksInfo.getTitle() + ":";
                                }
                                String value = "";
                                if (StringUtils.isNotEmpty(remarksInfo.getField())) {
                                    if (beanWrapper.isWritableProperty(remarksInfo.getField())) {
                                        value =
                                                beanWrapper
                                                        .getPropertyValue(remarksInfo.getField())
                                                        .toString();
                                    }
                                }
                                // 下拉框需要转义
                                if (StringUtils.isNotBlank(value)) {
                                    if ("select".equals(remarksInfo.getType())) {
                                        List<SelectVO> dictList =
                                                dictIntegration.getDictByDictField(
                                                        remarksInfo.getField());
                                        if (!CollectionUtils.isEmpty(dictList)) {
                                            for (SelectVO selectVO : dictList) {
                                                // 字段相同转义
                                                if (value.equals(selectVO.getDictKey())) {
                                                    value = selectVO.getDictValue();
                                                    break;
                                                }
                                            }
                                        }
                                    }
                                }
                                if (StringUtils.isNotBlank(value)) {
                                    remarks = remarks + value;
                                }
                                if (i < size) {
                                    remarks = remarks + "；";
                                }
                                i++;
                            }
                        }

                        /*if (encryptionFlag) {
                            remarks = CommonSecureUtil.aesEncrypt(remarks);
                        }*/
                        updateOplog.lambda().set(Oplog::getRemarks, remarks);
                        updateOplogHi.lambda().set(OplogHistory::getRemarks, remarks);
                    } catch (Exception e) {
                        logger.error("获取方法摘要异常：", e);
                    }
                }
                OplogUtil.update(oplogInfo);
                if (oplogInfo.getOplogId() != null) {
                    updateOplog
                            .lambda()
                            .eq(Oplog::getOplogId, oplodId)
                            .set(Oplog::getFuncId, opApi.funcCode())
                            .set(Oplog::getTitle, oplogFunction.getFuncTitle());
                    updateOplogHi
                            .lambda()
                            .eq(OplogHistory::getOplogId, oplodId)
                            .set(OplogHistory::getFuncId, opApi.funcCode())
                            .set(OplogHistory::getTitle, oplogFunction.getFuncTitle());
                    oplogService.update(updateOplog);
                    if (oplogHistoryFlag) {
                        oplogHistoryService.update(updateOplogHi);
                    }
                }
            }
        }

        if (saveOplogApi == null) {
            saveOplogApi = true;
        }
        OplogApiVO oplogApiVO = new OplogApiVO();
        String reqLogId = IdUtil.fastSimpleUUID();
        oplogApiVO.setReqLogId(reqLogId);
        oplogApiVO.setBegTime(new Date());
        try {
            HttpServletRequest request =
                    ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes())
                            .getRequest();
            if (request != null) {
                String ip = ServletUtil.getClientIP(request);
                String api = request.getServletPath();
                oplogApiVO.setIp(ip);
                oplogApiVO.setReqApi(api);
                oplogApiVO.setServerPort(request.getLocalPort());
                oplogApiVO.setServletContext(request.getContextPath().replace("/", ""));
            }
        } catch (Exception e) {

        }
        // 获取目标对象对应的类名
        String className = joinPoint.getTarget().getClass().getName();
        // 获取目标对象上正在执行的方法名
        String methodName = joinPoint.getSignature().getName();
        oplogApiVO.setClassPath(className);
        oplogApiVO.setMethod(methodName);
        oplogApiVO.setTitle(opApi.title());
        oplogApiVO.setFuncId(opApi.funcCode());
        oplogApiVO.setFuncCode(opApi.funcCode());
        oplogApiVO.setFuncType(opApi.funcType().getType());
        oplogApiVO.setFuncTypeName(opApi.funcType().getText());
        try {
            oplogApiVO.setServerIp(ServiceRequestUtil.getLocalIP());
        } catch (Exception e) {

        }
        HostInfo hostInfo = SystemUtil.getHostInfo();
        if (hostInfo != null) {
            if (StringUtils.isBlank(oplogApiVO.getServerIp())) {
                oplogApiVO.setServerIp(hostInfo.getAddress());
            }
            if (StringUtils.isBlank(oplogApiVO.getServletContext())) {
                oplogApiVO.setServletContext(hostInfo.getName());
            }
        }

        Object ret;
        try {

            oplogApiVO.setCode("0");
            oplogApiVO.setMessage("成功");
            String reqBody = "";
            if (args != null && args.length > 0) {
                if (args.length > 1) {
                    List<Object> list = new ArrayList<>();
                    for (int i = 0; i < args.length; i++) {
                        if (args[i] instanceof ServletRequest
                                || args[i] instanceof HttpServletRequest
                                || args[i] instanceof HttpServletResponse
                                || args[i] instanceof ServletResponse
                                || args[i] instanceof MultipartFile
                                || args[i] instanceof File) {
                            continue;
                        }
                        list.add(args[i]);
                    }
                    reqBody = JSONUtil.toJsonStr(list);
                } else {
                    reqBody = JSONUtil.toJsonStr(args[0]);
                }
                if (encryptionFlag) {
                    reqBody = CommonSecureUtil.aesEncrypt(reqBody);
                }
                oplogApiVO.setReqBody(reqBody);
                for (int i = 0; i < args.length; i++) {
                    Object obj = args[i];
                    if (obj instanceof BaseVO) {
                        BaseVO baseVO = (BaseVO) obj;
                        oplogApiVO.setRouterId(baseVO.getOpRouterId());
                        oplogApiVO.setAccessToken(baseVO.getAccessToken());
                        oplogApiVO.setOplogId(oplodId);
                        baseVO.setOpOplogId(oplodId);
                        if (StringUtils.isBlank(oplogApiVO.getIp())) {
                            oplogApiVO.setIp(baseVO.getReqIp());
                        }
                        baseVO.setOpFuncId(opApi.funcCode());
                        break;
                    } else if (obj instanceof BasePageVO) {
                        BasePageVO basePageVO = (BasePageVO) obj;
                        oplogApiVO.setRouterId(basePageVO.getOpRouterId());
                        oplogApiVO.setAccessToken(basePageVO.getAccessToken());
                        oplogApiVO.setOplogId(oplodId);
                        basePageVO.setOpOplogId(oplodId);
                        if (StringUtils.isBlank(oplogApiVO.getIp())) {
                            oplogApiVO.setIp(basePageVO.getReqIp());
                        }
                        basePageVO.setOpFuncId(opApi.funcCode());
                        break;
                    } else if (obj instanceof BaseAuditVO) {
                        BaseAuditVO baseAuditVO = (BaseAuditVO) obj;
                        oplogApiVO.setRouterId(baseAuditVO.getOpRouterId());
                        oplogApiVO.setAccessToken(baseAuditVO.getAccessToken());
                        oplogApiVO.setOplogId(oplodId);
                        baseAuditVO.setOpOplogId(oplodId);
                        if (StringUtils.isBlank(oplogApiVO.getIp())) {
                            oplogApiVO.setIp(baseAuditVO.getReqIp());
                        }
                        baseAuditVO.setOpFuncId(opApi.funcCode());
                        break;
                    }
                }
            }

            String accessToken = oplogApiVO.getAccessToken();
            SessionInfo sessionInfo = new SessionInfo();
            Boolean tokenFlag = false;
            String redisKey = CacheConst.ACCESS_TOKEN + ":" + accessToken;
            if (StringUtils.isNotBlank(accessToken)) {
                if (redisHelper.hasKey(redisKey)) {
                    tokenFlag = true;
                    sessionInfo = (SessionInfo) redisHelper.get(redisKey);
                    SessionUtil.setSessionInfo(sessionInfo);
                }
            }
            if (opApi.publicFlag().getType().equals(BoolEnum.FALSE.getType())) {
                // token校验
                if (StringUtils.isBlank(accessToken)) {
                    throw new AppException(ErrorEnum.SYSTEM_ERROR_NO_LONIN);
                }

                if (!tokenFlag) {
                    throw new AppException(ErrorEnum.SYSTEM_ERROR_NO_LONIN);
                }
                if (System.currentTimeMillis() - sessionInfo.getExpireFreshTime()
                        > sessionInfo.getExpireMinute() * 1000) {
                    throw new AppException(ErrorEnum.SYSTEM_ERROR_NO_LONIN);
                }
                if (EquipmentTypeEnum.PC.getType().equals(sessionInfo.getEquipmentType())) {
                    if (System.currentTimeMillis() - sessionInfo.getExpireFreshTime()
                            < sessionInfo.getExpireMinute() / 2) {
                        // pc端系统 过期时间过了一半，有请求的话，刷新
                        sessionInfo.setExpireFreshTime(System.currentTimeMillis());
                        redisHelper.set(redisKey, sessionInfo, timeout);
                    }
                }

                // 权限校验
                boolean checkPrivilege = sessionInfo.isCheckPrivilege();
                if (opApi.checkPrivilege().getType().equals(BoolEnum.FALSE.getType())) {
                    checkPrivilege = false;
                }
                if (auFlag) {
                    checkPrivilege = false;
                }
                if (checkPrivilege) {
                    // 权限校验
                    Set<String> systemPrivileges = sessionInfo.getSystemPrivileges();
                    if (!CollectionUtils.isEmpty(systemPrivileges)) {
                        if (systemPrivileges.contains(SysPermissionEnum.superadmin.getType())) {
                            // 超级管理员 不控制权限
                            checkPrivilege = false;
                        }
                        if (systemPrivileges.contains(SysPermissionEnum.allfunction.getType())) {
                            // 所有功能可操作
                            checkPrivilege = false;
                        }
                    }
                }
                if (checkPrivilege) {
                    if (StringUtils.isNotBlank(oplogApiVO.getRouterId())) {
                        // 管理系统会传入路由，权限根据路由下配置
                        Map<String, Set<String>> routers = sessionInfo.getRouters();
                        Set<String> funcCodeList = routers.get(oplogApiVO.getRouterId());
                        if (CollectionUtils.isEmpty(funcCodeList)) {
                            throw new AppException(ErrorEnum.SYSTEM_ERROR_NO_PRIVILEGE);
                        }
                        if (!funcCodeList.contains(oplogApiVO.getFuncCode())) {
                            throw new AppException(ErrorEnum.SYSTEM_ERROR_NO_PRIVILEGE);
                        }
                    } else {
                        Set<String> apiList = sessionInfo.getApiList();
                        if (CollectionUtils.isEmpty(apiList)) {
                            throw new AppException(ErrorEnum.SYSTEM_ERROR_NO_PRIVILEGE);
                        }

                        if (!apiList.contains(oplogApiVO.getFuncCode())) {
                            throw new AppException(ErrorEnum.SYSTEM_ERROR_NO_PRIVILEGE);
                        }
                    }
                }
            }
            ret = joinPoint.proceed();

            Object[] argsNew = joinPoint.getArgs();
            for (int i = 0; i < argsNew.length; i++) {
                Object obj = argsNew[i];
                if (obj instanceof BaseVO) {
                    BaseVO baseVO = (BaseVO) obj;
                    break;
                } else if (obj instanceof BasePageVO) {
                    BasePageVO basePageVO = (BasePageVO) obj;
                    break;
                } else if (obj instanceof BaseAuditVO) {
                    BaseAuditVO baseAuditVO = (BaseAuditVO) obj;
                    break;
                }
            }

            if (ret != null) {
                String resBody = "";
                if (ret instanceof BaseVO
                        || ret instanceof BasePageVO
                        || ret instanceof BaseAuditVO
                        || ret instanceof Map
                        || ret instanceof List) {
                    resBody = JSONUtil.toJsonStr(ret);
                } else if (ret instanceof String) {
                    resBody = ret.toString();
                } else {
                    resBody = JSONUtil.toJsonStr(ret);
                }
                if (encryptionFlag) {
                    resBody = CommonSecureUtil.aesEncrypt(resBody);
                }
                oplogApiVO.setResBody(resBody);
            }
        } catch (AppException e) {
            if (e.getCode() != null) {
                oplogApiVO.setCode(e.getCode().toString());
            } else {
                oplogApiVO.setCode("-1");
            }
            oplogApiVO.setMessage(e.getMsg());
            throw e;
        } catch (Exception e) {
            oplogApiVO.setCode("-1");
            oplogApiVO.setMessage(e.getMessage());
            throw e;
        } finally {
            oplogApiVO.setEndTime(new Date());
            if (saveOplogApi && !BoolEnum.FALSE.getType().equals(logFlag)) {
                oplogIntegration.saveApiOpLog(oplogApiVO);
            }
        }
        return ret;
    }
}
